package org.gist.classloader;

import org.gist.classloader.exception.GclException;



/**
 * A factory class that loads classes from specified JarClassLoader and tries to
 * instantiate their objects
 * 
 * @author Suresh Reddy Guntaka
 * 
 */
@SuppressWarnings("unchecked")
public class GclObjectFactory {
    private static GclObjectFactory gclObjectFactory = new GclObjectFactory();
    private static boolean autoProxy;

    /**
     * private constructor
     */
    private GclObjectFactory() {
        autoProxy = Configuration.autoProxy();
    }

    /**
     * Returns the instance of the singleton factory
     * 
     * @return GclObjectFactory
     */
    public static GclObjectFactory getInstance() {
        return gclObjectFactory;
    }

    /**
     * Returns the instance of the singleton factory that can be used to create
     * auto proxies for jcl-created objects
     * 
     * @return GclObjectFactory
     */
    public static GclObjectFactory getInstance(boolean autoProxy) {
        GclObjectFactory.autoProxy = autoProxy;
        return gclObjectFactory;
    }

    /**
     * Creates the object of the specified class from the specified class loader
     * by invoking the default constructor
     * 
     * @param jcl
     * @param className
     * @return Object
     */
    public Object create(JarClassLoader jcl, String className) {
        return create( jcl, className, (Object[]) null );
    }

    /**
     * Creates the object of the specified class from the specified class loader
     * by invoking the right arguments-constructor
     * 
     * @param jcl
     * @param className
     * @param args
     * @return Object
     */
    public Object create(JarClassLoader jcl, String className, Object... args) {
        if( args == null || args.length == 0 ) {
            try {
                return newInstance( jcl.loadClass( className ).newInstance() );
            } catch (Throwable e) {
                throw new GclException( e );
            }
        }

        Class[] types = new Class[args.length];

        for( int i = 0; i < args.length; i++ )
            types[i] = args[i].getClass();

        return create( jcl, className, args, types );
    }

    /**
     * Creates the object of the specified class from the specified class loader
     * by invoking the right arguments-constructor based on the passed types
     * parameter
     * 
     * @param jcl
     * @param className
     * @param args
     * @param types
     * @return Object
     */
    public Object create(JarClassLoader jcl, String className, Object[] args, Class[] types) {
        Object obj = null;

        if( args == null || args.length == 0 ) {
            try {
                obj = jcl.loadClass( className ).newInstance();
            } catch (Throwable e) {
                throw new GclException( e );
            }
        } else {
            try {
                obj = jcl.loadClass( className ).getConstructor( types ).newInstance( args );
            } catch (Exception e) {
                throw new GclException( e );
            }
        }

        return newInstance( obj );
    }

    /**
     * Creates the object of the specified class from the specified class loader
     * by invoking the right static factory method
     * 
     * @param jcl
     * @param className
     * @param methodName
     * @param args
     * @return Object
     */
    public Object create(JarClassLoader jcl, String className, String methodName, Object... args) {
        if( args == null || args.length == 0 ) {
            try {
                return newInstance( jcl.loadClass( className ).getMethod( methodName ).invoke( null ) );
            } catch (Exception e) {
                throw new GclException( e );
            }
        }
        Class[] types = new Class[args.length];

        for( int i = 0; i < args.length; i++ )
            types[i] = args[i].getClass();

        return create( jcl, className, methodName, args, types );
    }

    /**
     * Creates the object of the specified class from the specified class loader
     * by invoking the right static factory method based on the types parameter
     * 
     * @param jcl
     * @param className
     * @param methodName
     * @param args
     * @param types
     * @return Object
     */
    public Object create(JarClassLoader jcl, String className, String methodName, Object[] args, Class[] types) {
        Object obj = null;
        if( args == null || args.length == 0 ) {
            try {
                obj = jcl.loadClass( className ).getMethod( methodName ).invoke( null );
            } catch (Exception e) {
                throw new GclException( e );
            }
        } else {
            try {
                obj = jcl.loadClass( className ).getMethod( methodName, types ).invoke( null, args );
            } catch (Exception e) {
                throw new GclException( e );
            }
        }

        return newInstance( obj );
    }

    /**
     * Creates a proxy
     * 
     * @param object
     * @return
     */
    private Object newInstance(Object object) {
        if( autoProxy ) {
            return GclUtils.toCastable( object );
        }

        return object;
    }
}
